package com.binarysoft.service.common.multiconnpool;

import java.net.URL;
import java.sql.Connection;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import common.framework.dsb.AbstractServiceBean;
import common.framework.dsb.annotation.DSBService;
import common.framework.dsb.service.ServiceContext;
import common.framework.jaxb.JAXBTool;
import common.framework.log.Logger;

@DSBService(name = "DSB/MultipleConnectionPoolService")
public class MultipleConnectionPoolServiceBean extends AbstractServiceBean implements MultipleConnectionPoolService {
	private Hashtable<String, ComboPooledDataSource> pools = new Hashtable<String, ComboPooledDataSource>();
	private Map<Connection, String> stackTrace = new Hashtable<Connection, String>();

	private long connectionCheckInterval = 10000;
	private boolean stackTraceEnabled = false;
	private boolean isClosed = false;
	private String poolFiles = null;

	@Override
	public void start(ServiceContext serviceContext) throws Exception {
		super.start(serviceContext);
		loadGlobalConfigs();
		loadPools();
		ConnectionChecker checker = new ConnectionChecker();
		checker.setName("DB-connection-checker");
		checker.start();
	}

	@Override
	public void close() throws Exception {
		isClosed = true;
		for (ComboPooledDataSource dataSource : pools.values()) {
			dataSource.close();
		}
		pools.clear();
	}

	@Override
	public void refresh() throws Exception {
		close();
		loadGlobalConfigs();
		loadPools();
	}

	@Override
	public Map<Connection, String> getStackTrace() {
		Map<Connection, String> result = new HashMap<Connection, String>(stackTrace);

		return result;
	}
	
	@Override
	public ComboPooledDataSource getDataSource(String poolname) throws Exception {
		ComboPooledDataSource comboPooledDataSource = pools.get(poolname);
		if (comboPooledDataSource == null) {
			throw new Exception("Connection Pool[" + poolname + "] not found");
		}
		
		return comboPooledDataSource;
	}

	@Override
	public Connection getConnection(String poolname) throws Exception {
		ComboPooledDataSource comboPooledDataSource = pools.get(poolname);
		if (comboPooledDataSource == null) {
			throw new Exception("Connection Pool[" + poolname + "] not found");
		}
		Connection conn = comboPooledDataSource.getConnection();

		if (stackTraceEnabled) {
			Thread th = Thread.currentThread();
			StringBuffer sb = new StringBuffer();
			StackTraceElement[] stackTraces = th.getStackTrace();
			for (int j = 0; j < stackTraces.length; j++) {
				StackTraceElement ste = stackTraces[j];
				sb.append("  at " + ste.toString() + "\n");
			}
			stackTrace.put(conn, sb.toString());
		}
		return conn;
	}

	@Override
	public boolean exists(String poolname) {
		return pools.contains(poolname);
	}

	@Override
	public Collection<String> listPools() {
		return new HashSet<String>(pools.keySet());
	}

	public int getNumBusyConnections(String poolname) throws Exception {
		ComboPooledDataSource comboPooledDataSource = pools.get(poolname);
		if (comboPooledDataSource == null) {
			throw new Exception("Connection Pool[" + poolname + "] not found");
		}
		return comboPooledDataSource.getNumBusyConnections();
	}

	public int getNumConnections(String poolname) throws Exception {
		ComboPooledDataSource comboPooledDataSource = pools.get(poolname);
		if (comboPooledDataSource == null) {
			throw new Exception("Connection Pool[" + poolname + "] not found");
		}
		return comboPooledDataSource.getNumConnections();
	}

	public int getNumIdleConnections(String poolname) throws Exception {
		ComboPooledDataSource comboPooledDataSource = pools.get(poolname);
		if (comboPooledDataSource == null) {
			throw new Exception("Connection Pool[" + poolname + "] not found");
		}
		return comboPooledDataSource.getNumIdleConnections();
	}

	public int getInitialPoolSize(String poolname) throws Exception {
		ComboPooledDataSource comboPooledDataSource = pools.get(poolname);
		if (comboPooledDataSource == null) {
			throw new Exception("Connection Pool[" + poolname + "] not found");
		}
		return comboPooledDataSource.getInitialPoolSize();
	}

	@SuppressWarnings("unchecked")
	public void loadPoolConfig(String configFileName) throws Exception {
		URL configFileURL = serviceContext.getConfigFile(configFileName);
		String packageName = this.getClass().getPackage().getName();
		JAXBElement<MultiConnPoolType> jaxbElement = (JAXBElement<MultiConnPoolType>) JAXBTool.unmarshal(configFileURL, packageName, this.getClass().getClassLoader());
		MultiConnPoolType multiConnPoolType = jaxbElement.getValue();
		List<ConnPoolType> connpools = multiConnPoolType.getConnPool();
		for (ConnPoolType connPoolType : connpools) {
			if (!pools.containsKey(connPoolType.getPoolName())) {
				ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
				comboPooledDataSource.setDriverClass(connPoolType.getDriverClass());
				comboPooledDataSource.setJdbcUrl(connPoolType.getJdbcUrl());
				comboPooledDataSource.setUser(connPoolType.getUserName());
				comboPooledDataSource.setPassword(connPoolType.getPassword());
				comboPooledDataSource.setInitialPoolSize(connPoolType.getInitPoolSize());
				comboPooledDataSource.setMaxPoolSize(connPoolType.getMaxPoolSize());
				comboPooledDataSource.setMinPoolSize(connPoolType.getMinPoolSize());
				comboPooledDataSource.setAcquireIncrement(connPoolType.getAcquireIncrement());
				comboPooledDataSource.setIdleConnectionTestPeriod(connPoolType.getIdleConnectionTestPeriod());
				comboPooledDataSource.setMaxIdleTime(connPoolType.getMaxIdleTime());
				comboPooledDataSource.setAutoCommitOnClose(connPoolType.isAutoCommitOnClose());
				comboPooledDataSource.setPreferredTestQuery(connPoolType.getPreferredTestQuery());
				comboPooledDataSource.setTestConnectionOnCheckout(connPoolType.isTestConnectionOnCheckout());
				comboPooledDataSource.setTestConnectionOnCheckin(connPoolType.isTestConnectionOnCheckin());
				comboPooledDataSource.setAcquireRetryAttempts(connPoolType.getAcquireRetryAttempts());
				comboPooledDataSource.setAcquireRetryDelay(connPoolType.getAcquireRetryDelay());
				comboPooledDataSource.setBreakAfterAcquireFailure(connPoolType.isBreakAfterAcquireFailure());
				pools.put(connPoolType.getPoolName(), comboPooledDataSource);
			}
		}
	}

	private void loadGlobalConfigs() throws Exception {
		this.serviceContext.loadConfigProperties("pools.properties");
		stackTraceEnabled = "true".equalsIgnoreCase(serviceContext.getProperty("pool.stacktrace.enabled"));
		connectionCheckInterval = Long.parseLong(serviceContext.getProperty("connection.checking.interval")) * 1000;
		poolFiles = serviceContext.getProperty("datasource.pool.files");
		Logger.log(Logger.LOW_LEVEL, "poolFiles: " + poolFiles);
	}

	private void loadPools() throws Exception {
		if (poolFiles != null && !"".equalsIgnoreCase(poolFiles)) {
			StringTokenizer stier = new StringTokenizer(poolFiles, ",");
			while (stier.hasMoreTokens()) {
				String poolFile = stier.nextToken();
				Logger.log(Logger.LOW_LEVEL, "load pool file:" + poolFile);
				loadPoolConfig(poolFile);
			}
		}
	}

	private class ConnectionChecker extends Thread {
		public void run() {
			while (!isClosed) {
				try {
					sleep(connectionCheckInterval);
				} catch (InterruptedException e) {
				}
				Map<Connection, String> clonedStackTrace = new HashMap<Connection, String>(stackTrace);
				try {
					for (Map.Entry<Connection, String> entry : clonedStackTrace.entrySet()) {
						Connection conn = entry.getKey();
						if (conn.isClosed()) {
							stackTrace.remove(conn);
						}
					}
				} catch (Throwable th) {
					th.printStackTrace(System.out);
				}
			}// while
		}// run
	}

	public static void main(String[] args) throws JAXBException {
		ObjectFactory objectFactory = new ObjectFactory();
		ConnPoolType connPoolType1 = objectFactory.createConnPoolType();
		connPoolType1.setPoolName("pool.server.dsb");
		connPoolType1.setJdbcUrl("jdbc:oracle:thin:@127.0.0.1:1521:xe");
		connPoolType1.setDriverClass("oracle.jdbc.driver.OracleDriver");
		connPoolType1.setAcquireIncrement(1);
		connPoolType1.setUserName("ems2");
		connPoolType1.setPassword("ems2");
		connPoolType1.setPreferredTestQuery("select sysdate from dual");
		connPoolType1.setAutoCommitOnClose(true);
		ConnPoolType connPoolType2 = objectFactory.createConnPoolType();
		connPoolType2.setPoolName("pool.driver.dsb");
		connPoolType2.setJdbcUrl("jdbc:oracle:thin:@127.0.0.1:1521:xe");
		connPoolType2.setAcquireIncrement(1);
		MultiConnPoolType multiConnPoolType = objectFactory.createMultiConnPoolType();
		multiConnPoolType.getConnPool().add(connPoolType1);
		multiConnPoolType.getConnPool().add(connPoolType2);
		JAXBElement<?> jaxbElement = objectFactory.createMultiConnPool(multiConnPoolType);
		byte[] bytes = JAXBTool.marshal(jaxbElement, MultiConnPoolType.class);
		System.out.println(new String(bytes));
	}


}
